package com.fzu.geometa.metadata.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fzu.geometa.metadata.model.po.Item;
import com.fzu.geometa.metadata.model.po.Namespace;
import com.fzu.geometa.metadata.model.po.Register;
import com.fzu.geometa.metadata.service.*;
import com.fzu.geometa.metadata.util.RedisKeyUtils;
import org.dom4j.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Transactional
public class MetadataServiceImpl implements MetadataService {
    @Autowired
    private RegisterService registerService;
    @Autowired
    private ItemService itemService;
    @Autowired
    private NamespaceService namespaceService;
    @Autowired
    private XmlService xmlService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public boolean registerMetadata(Register register, String content) {
        XmlService.ItemsAndNs itemsAndNs = xmlService.getItemsAndNs(content);
        List<Item> items = itemsAndNs.getItems();
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = itemsAndNs.getNamespaces();
        return saveMetaData(register,items,namespaces);
    }

    @Override
    public boolean registerMetadata(Register register, URL url) {
        XmlService.ItemsAndNs itemsAndNs = xmlService.getItemsAndNs(url);
        List<Item> items = itemsAndNs.getItems();
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = itemsAndNs.getNamespaces();
        return saveMetaData(register,items,namespaces);
    }

    @Override
    public boolean registerMetadata(Register register, InputStream inputStream) {
        XmlService.ItemsAndNs itemsAndNs = xmlService.getItemsAndNs(inputStream);
        List<Item> items = itemsAndNs.getItems();
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = itemsAndNs.getNamespaces();
        return saveMetaData(register,items,namespaces);
    }

    private boolean saveMetaData(Register register,List<Item> items,List<com.fzu.geometa.metadata.model.po.Namespace> namespaces) {
        boolean res = true;
        res &= registerService.save(register);
        // 设置 cid
        items.forEach((i) -> i.setCid(register.getCid()));
        namespaces.forEach((n) -> n.setCid(register.getCid()));
        res &= itemService.saveBatch(items);
        res &= namespaceService.saveBatch(namespaces);
        return res;
    }

    @Override
    public String getMetadata(String coverageId) {
        String res;
        // 先去缓存中查找
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String key = RedisKeyUtils.getMetadataKey(coverageId);
        res = ops.get(key);
        if (res != null) {
            return res;
        }

        // 缓存中不存在
        // 查看数据是否被注册
        Long cid = getCid(coverageId);
        if (cid == null) {
            // 未注册
            return null;
        }
        // 从数据库中查找
        res = getMetadata(cid);

        // 存入缓存，15 分钟
        ops.set(key,res,15, TimeUnit.MINUTES);
        return res;
    }

    @Override
    public boolean deleteMetaData(String coverageId) {
        Long cid = getCid(coverageId);
        if (cid == null) {
            return false;
        }
        // 删除各表数据
        deleteMetaData(cid);
        // 删除缓存
        redisTemplate.delete(RedisKeyUtils.getMetadataKey(coverageId));
        return true;
    }

    @Override
    public boolean deleteMetaData(Long cid) {
        boolean res = true;
        res &= registerService.removeById(cid);
        res &= itemService.removeByCid(cid);
        res &= namespaceService.removeByCid(cid);
        return res;
    }

    @Override
    public String getMetadata(Long cid) {
        // 查询 item
        List<Item> items = itemService.listByCid(cid);
        // 查询 namespace
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = namespaceService.listByCid(cid);
        String res = xmlService.getXmlString(items, namespaces);
        return res;
    }

    @Override
    public String query(String coverageId, String nodePath) {
        String metaData = getMetadata(coverageId);
        if (metaData == null) {
            return null;
        }
        Document doc;
        try {
            doc = DocumentHelper.parseText(metaData);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
        // 根据 XPath 查询节点
        Node node = doc.selectSingleNode(nodePath);
        return Optional.ofNullable(node)
                .map(Node::asXML)
                .orElse(null);
    }

    @Override
    public boolean del(String coverageId, String nodePath) {
        // 不能查询缓存，要获得真实的 eid
        // 查询数据库
        Long cid = getCid(coverageId);
        if (cid == null) {
            return false;
        }
        List<Item> items = itemService.listByCid(cid);
        List<com.fzu.geometa.metadata.model.po.Namespace> namespaces = namespaceService.listByCid(cid);

        // 获得文档
        Map<Element, Long> idMap = new HashMap<>();
        Document doc = xmlService.getDocAndIdMap(items, namespaces, idMap);
        // 选择待删除的节点
        Node node = doc.selectSingleNode(nodePath);
        Element element;
        if (node instanceof Element) {
            element = (Element) node;
        } else {
            return false;
        }

        // 1. 查询删除的 item
        Long eid = idMap.get(element);
        Item item = items
                .stream()
                .filter(i-> i.getEid().equals(eid))
                .findFirst()
                .get();
        Integer order = item.getOrder();
        // 2. 查询兄弟节点
        List<Item> brothers = items.stream()
                .filter(i -> Objects.equals(i.getPid(), item.getPid()))
                .collect(Collectors.toList());
        // 3. 修改 order 并提交
        itemService.updateBatchById(brothers
                .stream()
                .filter(i->i.getOrder() > order)
                .peek(i->i.setOrder(i.getOrder() - 1))
                .collect(Collectors.toList()));
        // 4.查找待删除 id
        List<Long> delIds = findId(element, idMap);
        // 执行删除
        itemService.removeBatchByIds(delIds);
        // 缓存失效
//        cacheService.remove(coverageId);
        return true;
    }

    @Override
    public boolean add(String coverageId, String nodePath, String content, Integer seq) {
        // seq 起始值为0，xpath起始索引为1，注意区分
        if (seq != null && seq < 0) {
            return false;
        }
        // 不能查询缓存，要获得真实的 eid
        // 查询数据库
        Long cid = getCid(coverageId);
        if (cid == null) {
            return false;
        }
        List<Item> items = itemService.listByCid(cid);
        List<Namespace> namespaces = namespaceService.listByCid(cid);

        // 获得文档
        Map<Element, Long> idMap = new HashMap<>();
        Document doc = xmlService.getDocAndIdMap(items, namespaces, idMap);
        // 查询被插入的节点
        Node node = doc.selectSingleNode(nodePath);
        Element root;
        if (node instanceof Element) {
            root = (Element) node;
        } else {
            return false;
        }
        // 获得被插入节点的 id
        Long eid = idMap.get(root);
        // 将待插入的内容转化为 item
        // item 是 bfs 序排列的
        List<Item> addition = xmlService.getItems(content);
        if (addition == null) {
            return false;
        }

        // 设置插入根节点的 pid
        addition.get(0).setPid(eid);
        // 设置插入节点的 cid
        addition.forEach(i->i.setCid(cid));
        // 查询兄弟节点
        List<Item> brothers = items.stream()
                .filter(i -> Objects.equals(i.getPid(), eid))
                .collect(Collectors.toList());

        // seq 为空时，插入到最后
        if (seq == null) {
            seq = brothers.size();
        }
        if (seq > brothers.size()) {
            return false;
        }
        // 设置插入根节点的 order
        addition.get(0).setOrder(seq);
        // 修改兄弟节点的 order
        // 并将被修改的节点保存到数据库
        Integer finalSeq = seq;
        itemService.updateBatchById(brothers
                .stream()
                .filter(i->i.getOrder() >= finalSeq)
                .peek(i->i.setOrder(i.getOrder() + 1))
                .collect(Collectors.toList()));
        // 插入节点
        itemService.saveBatch(addition);
        // 缓存失效
//        cacheService.remove(coverageId);
        return true;
    }

    @Override
    public boolean removeBatch(List<Long> ids) {
        boolean res= registerService.removeBatchByIds(ids);
        res &= itemService.removeBatchByCid(ids);
        res &= namespaceService.removeBatchByCid(ids);
        return res;
    }

    // 查询节点及其子节点的id
    private List<Long> findId(Element root, Map<Element, Long> idMap) {
        if (root == null || idMap == null) {
            return null;
        }
        // bfs
        List<Long> ids = new ArrayList<>();
        Queue<Element> que = new LinkedList<>();
        que.offer(root);
        while (!que.isEmpty()) {
            Element element = que.poll();
            if (idMap.containsKey(element)) {
                ids.add(idMap.get(element));
            }

            for (Element e : element.elements()) {
                que.offer(e);
            }
        }
        return ids;
    }

    //查询cid
    private Long getCid(String coverageId) {
        LambdaQueryWrapper<Register> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Register::getCoverageId, coverageId);
        Register register = registerService.getOne(lqw);
        Long cid = register == null ? null : register.getCid();
        return cid;
    }
}
